// Copyright 2007 Google, Inc. All Rights Reserved.

package com.google.jessewilson.codesearch.configuration;

import com.google.jessewilson.codesearch.api.SearchProvider;
import com.google.jessewilson.codesearch.io.jdom.EasyElement;
import com.google.jessewilson.codesearch.io.jdom.Externalizer;
import org.jdom.Element;

import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * An immutable snapshot of the configuration of the plugin.
 * Contains all search provider configurations, along with information
 * about which ones are enabled and which one is currently active.
 *
 * @author jessewilson
 */
public final class Configuration {

  /**
   * An ordered set of all possible search providers to whether or not they're
   * enabled.
   */
  private final Map<SearchProvider, Entry> entries;

  private Configuration(Builder builder) {
    this.entries = Collections.unmodifiableMap(
        new LinkedHashMap<SearchProvider, Entry>(builder.configurations));
  }

  public SearchProviderConfiguration getSearchProviderConfiguration(
      SearchProvider searchProvider) {
    return entries.get(searchProvider).getSearchProviderConfiguration();
  }

  public Collection<Entry> entries() {
    return entries.values();
  }

  public static class Entry {
    private final SearchProviderConfiguration searchProviderConfiguration;
    private final boolean enabled;

    private Entry(SearchProviderConfiguration searchProviderConfiguration, boolean enabled) {
      this.searchProviderConfiguration = searchProviderConfiguration;
      this.enabled = enabled;
    }

    public SearchProviderConfiguration getSearchProviderConfiguration() {
      return searchProviderConfiguration;
    }

    public boolean isEnabled() {
      return enabled;
    }
  }

  public static class Builder {
    private final Map<SearchProvider, Entry> configurations =
        new LinkedHashMap<SearchProvider, Entry>();

    public Builder addConfiguration(SearchProviderConfiguration searchProviderConfiguration, boolean enabled) {
      configurations.put(searchProviderConfiguration.getSearchProvider(),
          new Entry(searchProviderConfiguration, enabled));
      return this;
    }

    public Configuration build() {
      return new Configuration(this);
    }
  }

  public static class MyExternalizer implements Externalizer<Configuration> {
    private static final String CONFIGURATION = "configuration";
    private static final String CLASS = "class";
    private static final String ENABLED = "enabled";

    public void writeExternal(Element target, Configuration data) {
      EasyElement easyElement = new EasyElement(target);

      for (Entry entry : data.entries()) {
        SearchProviderConfiguration configuration = entry.getSearchProviderConfiguration();
        boolean enabled = entry.isEnabled();
        EasyElement searchProviderElement =
            writeSearchProviderConfiguration(configuration, enabled);
        easyElement.addChild(searchProviderElement);
      }
    }

    /**
     * Externalizes the given search provider configuration and then returns the
     * element written.
     */
    private EasyElement writeSearchProviderConfiguration(
        SearchProviderConfiguration configuration, boolean enabled) {
      EasyElement searchProviderElement = new EasyElement("searchProvider");
      Class<? extends SearchProviderConfiguration> configurationClass =
          configuration.getClass();
      searchProviderElement.putString(CLASS, configurationClass.getName());
      searchProviderElement.putBoolean(ENABLED, enabled);
      EasyElement configurationElement = new EasyElement(CONFIGURATION);
      searchProviderElement.addChild(configurationElement);

      Externalizer<SearchProviderConfiguration> externalizer = Finder.find(
          configurationClass.getName());
      externalizer.writeExternal(configurationElement.getElement(), configuration);
      return searchProviderElement;
    }

    public Configuration readExternal(Element source) {
      Configuration.Builder builder = new Configuration.Builder();

      EasyElement easyElement = new EasyElement(source);

      for (EasyElement searchProviderElement : easyElement.getChildren()) {
        String className = searchProviderElement.getString(CLASS, null);
        boolean enabled = searchProviderElement.getBoolean(ENABLED, false);
        Element configurationElement = searchProviderElement.getElement().getChild(CONFIGURATION);
        SearchProviderConfiguration configuration =
            readSearchProviderConfiguration(className, configurationElement);

        builder.addConfiguration(configuration, enabled);
      }

      return builder.build();
    }

    /**
     * Constructs a search provider configuration object from the given
     * configuration element and class.
     */
    private SearchProviderConfiguration readSearchProviderConfiguration(
        String className, Element configurationElement) {
      Externalizer<SearchProviderConfiguration> externalizer = Finder.find(className);
      return externalizer.readExternal(configurationElement);
    }
  }
}
